/****************************************************************************

	COPYRIGHT(C) MAXSI SOFTWARE, JONAS 'SORTIE' TERMANSEN 2008, 2009, 2010

    This file is part of Maxsi Distribution.

    Maxsi Distribution is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Maxsi Distribution is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Maxsi Distribution. If not, see 7<http://www.gnu.org/licenses/>.

	*/ #include "MaxsiDistributionContributors.h" /*

	If you modify this file, please enter your name below and provide contact
	information in MaxsiDistributionContributors.h. For more information please see
	MaxsiDistributionContributors.h.
	
	Contributors to this file:

	- Jonas 'Sortie' Termansen
	- [your name here]

	MaxsiDistribution.dll
	Distributing since 2008

	ThreadInstall.cpp
	The thread that manages the whole install business!

****************************************************************************/

#include "MaxsiEngine.h"
#include "MaxsiDistributionLink.h"
#include "Thread.h"
#include "Data.h"
#include "General.h"
#include "MaxsiTorrent.h"
#include "Assert.h"
#include "Math.h"

#include <io.h>
#include <stdio.h>
#include <direct.h>
#include <SYS\Stat.h>
#include <fcntl.h>

ME_THREAD_LINK(ThreadInstall,ClassThreadInstall,MDLINKCPP);

// Interface for overriding local commands.

DECLARE_FUNCTION(bool, ErrorFunc,		(const char* Error))		=	(type_ErrorFunc)		&Error;
DECLARE_FUNCTION(bool, InfoFunc,		(const char* Error))		=	(type_InfoFunc)			&Info;
DECLARE_FUNCTION(bool, NewsUpdatedFunc,	(const char* News))			=	(type_NewsUpdatedFunc)	&NewsUpdated;
DECLARE_FUNCTION(bool, StatusFunc,		(const char* Status))		=	(type_StatusFunc)		&SetStatus;
DECLARE_FUNCTION(bool, SubStatusFunc,	(const char* Status))		=	(type_SubStatusFunc)	&SetSubStatus;
DECLARE_FUNCTION(bool, ProgressFunc,	(int Progress))				=	(type_ProgressFunc)		&SetProgress;
DECLARE_FUNCTION(bool, CheckBlockStates,	(MaxsiTorrentData* Data))	=	(type_CheckBlockStates)	&CheckBlockStatesReliable;
DECLARE_FUNCTION(bool, DownloadFunc,	())						=	0;
DECLARE_FUNCTION(bool, NoInternet,		())						=	0;
DECLARE_FUNCTION(bool, NeedsUpdating,	())						=	0;
DECLARE_FUNCTION(bool, DownloadNews,	(char* Host, char* Resource, char* Product, char* Format))				=	(type_DownloadNews)		&DownloadNewsFunc;

int		MIS_CurrentlyParsedTorrentId	=	-1;
List	MIS_TempHashes					=	0;
size_t	MIS_TempHashesNum				=	0;
List	MIS_TempFiles					=	0;
size_t	MIS_TempFilesNum				=	0;

ME_TorrentServer*	gTorrentServer		=	NULL;

// Use instead of d_ClassThreadInstall if IsIngame == true
ClassThreadInstall*	g_ClassThreadInstall	=	0;

bool Error(const char* Error)
{
	if ( IsIngame ) { return false; }
	char* NewError = BuildString(1,Error);
	SendMessage(d_ClassThreadInstall->Parent,WM_COMMAND,MSG_RecieveError,(LPARAM)NewError);
	return true;
}

bool NewsUpdated(const char* News)
{
	if ( IsIngame ) { return false; }
	char* NewNews = BuildString(1,News);
	SendMessage(d_ClassThreadInstall->Parent,WM_COMMAND,MSG_RecieveNews,(LPARAM)NewNews);
	return true;
}

bool Info(const char* Error)
{
	if ( IsIngame ) { return false; }
	// TODO: WHY DOESN'T IT TRANSMIT IT TO THE MAIN THREAD??
	MessageBoxA(d_ClassThreadInstall->Parent,Error,"Maxsi Installer",MB_ICONINFORMATION);
	return true;
}

bool SetStatus(const char* Status)
{
	if ( IsIngame ) { return false; }
	char* NewStatus = BuildString(1,Status);
	SendMessage(d_ClassThreadInstall->Parent,WM_COMMAND,MSG_SetStatus,(LPARAM)NewStatus);
	return true;
}

bool SetSubStatus(const char* Status)
{
	if ( IsIngame ) { return false; }
	char* NewStatus = BuildString(1,Status);
	SendMessage(d_ClassThreadInstall->Parent,WM_COMMAND,MSG_SetSubStatus,(LPARAM)NewStatus);
	return true;
}

bool SetProgress(int Progress)
{
	if ( IsIngame ) { return false; }
	SendMessage(d_ClassThreadInstall->Parent,WM_COMMAND,MSG_SetProgress,(LPARAM)Progress);
	return true;
}


bool ShowFailPage()
{
	Win7TaskBarSetState(d_ClassThreadInstall->Parent,TBPF_NOPROGRESS);
	if ( IsIngame ) { return false; }
	SendMessage(d_ClassThreadInstall->Parent,WM_COMMAND,GotoFailurePage,0);
	return true;
}

bool ShowConnectionPage()
{
	if ( IsIngame ) { return false; }
	SendMessage(d_ClassThreadInstall->Parent,WM_COMMAND,GotoConnectionPage,0);
	return true;
}

bool ShowErrorPage(char* Error)
{
	if ( IsIngame ) { return false; }
	char* Message = BuildString(1,Error);
	SendMessage(d_ClassThreadInstall->Parent,WM_COMMAND,GotoErrorPage,(LPARAM)Message);
	return true;
}

bool ShowPasswordPage()
{
	if ( IsIngame ) { return false; }
	SendMessage(d_ClassThreadInstall->Parent,WM_COMMAND,GotoPasswordPage,0);
	return true;
}

bool SendQuitMessage()
{
	if ( IsIngame ) { return false; }
	SendMessage(d_ClassThreadInstall->Parent,WM_COMMAND,QuitMessage,0);
	return true;
}

// TODO: This could be moved to MaxsiEngine.dll, or defined in the ME_THREAD Macro
BOOL ClassThreadInstall::InitApplication(HANDLE hInstance)
{	
    WNDCLASS  wc = {0};

	BaseClass::InitApplication(hInstance);

    wc.lpfnWndProc = (WNDPROC) ThreadInstallProc;
    wc.hInstance = (HINSTANCE)hInstance;
    wc.hIcon = 0;
    wc.hCursor = 0;
    wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); 
	wc.lpszMenuName = 0;
	wc.lpszClassName = TEXT("ClassThreadInstall");

	if (!RegisterClass(&wc)) { return FALSE; }

	return TRUE;
}

BOOL ClassThreadInstall::InitInstance(HINSTANCE hInstance, INT nCmdShow)
{	
	WindowHWND	=	CreateWindow(
        TEXT("ClassThreadInstall"),
        TEXT("ClassThreadInstall"),
		0,
        CW_USEDEFAULT, 0,
        500, 370,
        NULL, NULL, GetModuleHandle(NULL), NULL );

	if ( WindowHWND)
	{
		return TRUE;
	}

	return FALSE;
}

LRESULT APIENTRY ClassThreadInstall::WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    //HDC         hDC         = NULL;
    //PAINTSTRUCT ps          = {0};
    //RECT        rectClient  = {0};

	if ( message == ME_TorrentMessageID )
	{
		SOCKET	Socket	=	(SOCKET)wParam;
		int		Event	=	WSAGETSELECTEVENT(lParam);
		int		Error	=	WSAGETSELECTERROR(lParam);

		if ( Event == FD_CONNECT )
		{
			TorrentServer->OnClientConnected(Socket,Error);
		}
		else if ( Event == FD_CLOSE )
		{
			TorrentServer->OnClientDisconnected(Socket,Error);
		}

		return (0);
	}

	switch (message) 
    {
        case WM_COMMAND:
		{
			InstallProduct();
		}
		break;

		default:
		{
			return BaseClass::WindowProc(hWnd,message,wParam,lParam);
		}
    }
    return (0);
}

	LRESULT ClassThreadInstall::ME_Thread_Cmd(HWND hWnd, WPARAM wParam, LPARAM lParam)
	{
		switch (wParam)
		{
			// Handle Messages here
			case Torrent_MSG_SpeedUpdate:
			{
				OnSpeedUpdate(TorrentServer,-1);
			}	
			break;
			// Forward unknown messages to the BaseClass
			default:
				return (BaseClass::ME_Thread_Cmd(hWnd, wParam, lParam));
		}
		return (0);
	}


// Installs the product specified by the .mis script functions.
int		InstallProduct()
{
	ClassThreadInstall*	l_ClassThreadInstall	=	0;

	if ( IsIngame )
	{
		if ( g_ClassThreadInstall == NULL ) { g_ClassThreadInstall	=	new ClassThreadInstall; }
		l_ClassThreadInstall	=	g_ClassThreadInstall;
	}
	else
	{
		l_ClassThreadInstall	=	d_ClassThreadInstall;
	}
	
	Win7TaskBarSetState(l_ClassThreadInstall->Parent,TBPF_INDETERMINATE);

	char*	Error	=	0;

	// First validate if the data provided is valid, if not then complain
	if ( !InstallPath )
	{
		Error = "Critical Error: InstallPath is a null-pointer, aborting " SupportId(27);
		StatusFunc(Error);
		ErrorFunc(Error);
		ShowFailPage();
		return FALSE;
	}
	if ( !SteamUserName )
	{
		Error = "Critical Error: SteamUserName is a null-pointer, aborting " SupportId(28);
		StatusFunc(Error);
		ErrorFunc(Error);
		ShowFailPage();
		return FALSE;
	}

	StatusFunc("Initializing Installer...");

	// Local Variables
	if ( MapPack )
	{
		SourceModPath		=	BuildString(6,InstallPath,"SteamApps/",SteamUserName,"/",GameFolder,"/");
	}
	else
	{
		SourceModPath		=	BuildString(2,InstallPath,"SteamApps/SourceMods/");
	}

	// Initalize variables
	char*		CurrentDirectory	=	GetWorkingDirectory();
	if ( MDRoot == NULL )
				MDRoot				=	BuildString(2,SourceModPath,"Maxsi Distribution/");

	//l_ClassThreadInstall->FoundContentServer	=	false;
	l_ClassThreadInstall->TorrentServer			=	new ME_TorrentServer;
	gTorrentServer								=	l_ClassThreadInstall->TorrentServer;

	// Cache the torrent server pointer, both for speed and for easier coding
	ME_TorrentServer*	dTorrent	=	l_ClassThreadInstall->TorrentServer;

	char*		Log					=	BuildString(2,CurrentDirectory,"MaxsiDistributionTorrentDebug.log");

	dTorrent->SetDebugMode(true);
	dTorrent->ClearLogFile(Log);
	dTorrent->OpenLogFile(Log);

	delete[]	Log;

	// Set up the torrent server		
	dTorrent->OnSpeedUpdate		=	&OnSpeedUpdate;
	dTorrent->OnBufferExpanded	=	&OnBufferExpanded;	
	dTorrent->WindowHWND		=	l_ClassThreadInstall->WindowHWND;


	//HTTPParser_Struct	Reply;
	//memset(&Reply,0,sizeof(Reply));

	// Update the installer's progress field
	ProgressFunc(0);

	size_t		NumDownloads		=	GetListSize(DownloadName);
	//size_t		NumMDMirrors		=	GetListSize(MDMirrors)/2;

	
	dTorrent->TorrentData			=	new	MaxsiTorrentData*[NumDownloads];
	dTorrent->nTorrents				=	NumDownloads;

	bool		Success				=	true;

	if ( Register(
		ProductName,
		MasterServer,
		SteamUserName,
		Password,
		l_ClassThreadInstall) )
	{
		for (size_t J = 0; J < NumDownloads; J++)
		{
			if ( RunInstallScript(
				(char*)GetListEntry(DownloadName,J),
				MasterServer,
				SteamUserName,
				dTorrent,
				l_ClassThreadInstall,
				J) )
			{
				// Success
			}
			else
			{
				// Failure!
				Success		=	false;
			}
		}
	}
	else
	{
		// Connection issues!
		Success		=	false;

		// If we have a custom function that handles this, then let it do this,
		// otherwise we will do it ourselves.		
		if ( NoInternet ) { NoInternet(); } else
		{
			Error = "Couldn't contact the Maxsi Content Servers!\n\nIn order to install this product a connection needs to be established to the Maxsi Content Servers,"
				" the Maxsi Content Servers might currently be unavailable. Please ensure no firewalls are blocking access for this program on port 80.\n\nSorry for the inconvenience! " SupportId(29);
			StatusFunc("Couldn't contact the Maxsi Content Servers!");
			ErrorFunc(Error);
			ShowConnectionPage();
			Success		=	false;
		}
	}



	//	char*	Status		=	BuildString(3,"Getting information about ", (char*)GetListEntry(DownloadName,J), " from Content Servers...");
	//
	//	str_replace(Status,"+"," ");

	//	StatusFunc(Status);

	//	delete[] Status;

	//	l_ClassThreadInstall->FoundContentServer	=	false;

	//	dTorrent->TorrentData[J]					=	new	MaxsiTorrentData;
	//	dTorrent->TorrentData[J]->Server			=	l_ClassThreadInstall->TorrentServer;
	//	dTorrent->TorrentData[J]->TorrentName		=	BuildString(1,(char*)GetListEntry(DownloadName,J));
	//	dTorrent->TorrentData[J]->ContentArchive	=	BuildString(1,(char*)GetListEntry(DownloadName,J));
	//	dTorrent->TorrentData[J]->DirectoryName		=	BuildString(1,SourceModPath);
	//	dTorrent->TorrentData[J]->Id				=	J;

	//	MIS_CurrentlyParsedTorrentId				=	J;

	//	//for (size_t I = 0; I < NumMDMirrors; I++)
	//	//{
	//		DeleteHTTPParser_Struct(Reply);

	//		//char*		Resource		=	GetResourceFromURI((char*)GetListEntry(MDMirrors,I*2));
	//		//char*		Host			=	GetHostFromURI((char*)GetListEntry(MDMirrors,I*2));
	//		char*		Resource		=	GetResourceFromURI(MasterServer);
	//		char*		Host			=	GetHostFromURI(MasterServer);

	//		char*		DownloadURI		=	BuildString(9,Resource,"?action=register&product=",(char*)GetListEntry(DownloadName,J),"&installer=",InstallerInfo,"&username=",SteamUserName,"&password=",Password);
	//		str_replace(DownloadURI," ","+");
	//		char*		Request			=	BuildString(5,"GET ",DownloadURI," HTTP/1.1\r\nHost: ",Host,"\r\nConnection: Close\r\n\r\n");
	//		
	//					Reply			=	ConnectAndSendHTTPQuery(Request,strlen(Request),Host);

	//		//if	( !Reply.Out )
	//		//{
	//		//	// Couldn't connect. Perhaps there are DNS trouble? Try the static Ip!

	//		//	// Do we even have a static Ip?
	//		//	if ( strcmp((char*)GetListEntry(MDMirrors,I*2+1),"") != 0 )
	//		//	{
	//		//		// Yes we do! Then clean up
	//		//		DeleteHTTPParser_Struct(Reply);
	//		//		delete[]	Host;
	//		//		delete[]	Request;

	//		//		// And try to connect to the static Ip and see if we get any results!
	//		//		Host		=	BuildString(1,(char*)GetListEntry(MDMirrors,I*2+1));
	//		//		Request		=	BuildString(5,"GET ",DownloadURI," HTTP/1.1\r\nHost: ",Host,"\r\nConnection: Close\r\n\r\n");
	//		//		Reply		=	ConnectAndSendHTTPQuery(Request,strlen(Request),Host);
	//		//	}
	//		//}

	//		if	( Reply.Out )
	//		{
	//			// TODO: THE SERVER PROTOCOL CHANGED! YOU MUST NOW GET THE INSTALL SCRIPT
	//			// MANUALLY INSTEAD OF GETTING IT VIA ?ACTION=REGISTER!

	//			ME_DONT_COMPILE_RELEASE_BUILD();

	//			// Execute the install script
	//			char* InstallScript = new char[Reply.OutLenght+1];
	//			memcpy(InstallScript,Reply.Out,Reply.OutLenght);
	//			InstallScript[Reply.OutLenght] = 0;

	//			int ScriptResult	=	MISRun(InstallScript,"MaxsiInstaller");
	//			delete[] InstallScript;

	//			GenerateTorrentData();

	//			if ( J == 0 && IsIngame == false )
	//			{				
	//				// Connect to the news channel
	//				// Todo: Download all news for all the mods in the DownloadName array?
	//				DownloadNews(Host,Resource,(char*)GetListEntry(DownloadName,0),"text/plain");
	//			}

	//			// TODO! This should be done by the scripts

	//			 l_ClassThreadInstall->FoundContentServer =	true;
	//		}

	//		delete[]	Request;
	//		delete[]	DownloadURI;
	//		delete[]	Resource;
	//		delete[]	Host;

	//	//	// If the content of the mis script was valid, we found a content server and this boolean
	//	//	// value should have been set, thus we can safely break this loop.
	//	//	if ( l_ClassThreadInstall->FoundContentServer )
	//	//	{
	//	//		break;
	//	//	}
	//	//}

	//	if (!l_ClassThreadInstall->FoundContentServer)
	//	{	
	//		// If we have a custom function that handles this, then let it do this,
	//		// otherwise we will do it ourselves.		
	//		if ( NoInternet ) { NoInternet(); } else
	//		{
	//			Error = "Couldn't contact the Maxsi Content Servers!\n\nIn order to install this product a connection needs to be established to the Maxsi Content Servers,"
	//				" the Maxsi Content Servers might currently be unavailable. Please ensure no firewalls are blocking access for this program on port 80.\n\nSorry for the inconvenience! " SupportId(29);
	//			StatusFunc("Couldn't contact the Maxsi Content Servers!");
	//			ErrorFunc(Error);
	//			ShowConnectionPage();
	//			Success		=	false;
	//			break;
	//		}
	//	}
	//}

	WaitForPIDToClose();

	if ( Success )
	{
		for (size_t J = 0; J < NumDownloads; J++)
		{
			char*	Status		=	BuildString(3,"Scanning ", (char*)GetListEntry(DownloadName,J),"...");
		
			str_replace(Status,"+"," ");

			StatusFunc(Status);

			delete[] Status;

			// Detect whether some of the files already are downloaded!
			CheckBlockStates(dTorrent->TorrentData[J]);
		}

		ProgressFunc(0);

		ME_DONT_COMPILE_RELEASE_BUILD();

		if ( dTorrent->EvaluateServer() )
		{
			if ( DownloadFunc )
			{
				// If the actual download function is overriden -
				// use that version instead.
				DownloadFunc();
			}
			else
			{
				// Successfully connected to a content server!
				StatusFunc("Connecting to Content Servers...");

				// Begin the download!
				dTorrent->RunServer(MaxsiTorrentPort);
			}
		}

		ProgressFunc(100);

		// If we are here, then we succeded! I think.
		// TODO: Add error detection!
		StatusFunc("Download completed.");

		for ( size_t I = 0; I < dTorrent->nTorrents; I++ )
		{
			OutputContentArchiveState(dTorrent->TorrentData[I]);
		}

		if ( !IsIngame )
		{
			// Go to the final page		
			SendMessage(l_ClassThreadInstall->Parent,WM_COMMAND,GotoSuccessPage,0);
		}
	}

	// Clean up variables

	delete		l_ClassThreadInstall->TorrentServer;
	gTorrentServer						=	NULL;
	l_ClassThreadInstall->TorrentServer	=	NULL;
	delete[]	SourceModPath;		SourceModPath	=	0;

	if ( ContentArchive ) { delete[] ContentArchive; ContentArchive = 0; }

	if ( !IsIngame )
	{
		PostQuitMessage(0);
	}

	if ( IsIngame ) { delete g_ClassThreadInstall; g_ClassThreadInstall = NULL; }

	Win7TaskBarSetState(l_ClassThreadInstall->Parent,TBPF_NOPROGRESS);

	return TRUE;
}

bool	Register			(char* ProductName, char* MasterServer, char* SteamUserName, char* Password, ClassThreadInstall* l_ClassThreadInstall)
{
	bool		Result				=	false;

	HTTPParser_Struct	Reply;
	memset(&Reply,0,sizeof(Reply));

	char*		InstallerInfo		=	BuildString(1,InstallerDetail);
	char*		InstallerInfoEnc	=	EncodeURL(InstallerInfo);
	char*		ProductNameEnc		=	EncodeURL(ProductName);
	char*		SteamUserNameEnc	=	EncodeURL(SteamUserName);
	char*		PasswordEnc			=	EncodeURL(Password);
	
	char*		Resource			=	GetResourceFromURI(MasterServer);
	char*		Host				=	GetHostFromURI(MasterServer);

	char*		DownloadURI			=	BuildString(9,Resource,"?action=register&product=",ProductNameEnc,"&installer=",InstallerInfoEnc,"&username=",SteamUserNameEnc,"&password=",PasswordEnc);
	char*		Request				=	BuildString(5,"GET ",DownloadURI," HTTP/1.1\r\nHost: ",Host,"\r\nConnection: Close\r\n\r\n");
	
				Reply				=	ConnectAndSendHTTPQuery(Request,strlen(Request),Host);

	if	( Reply.Out )
	{
		// Execute the install script
		char*		InstallScript					=	new char[Reply.OutLenght+1];

		memcpy(InstallScript,Reply.Out,Reply.OutLenght);

		InstallScript[Reply.OutLenght]				=	0;

		l_ClassThreadInstall->ProductRegistered		=	false;

		/*int			ScriptResult					=*/	MISRun(InstallScript,"MaxsiInstaller");

		delete[]	InstallScript;

		Result	=	l_ClassThreadInstall->ProductRegistered;

		if ( IsIngame == false && Result == true )
		{				
			// Connect to the news channel
			DownloadNews(Host,Resource,ProductName,"text/plain");
		}
	}

	DeleteHTTPParser_Struct(Reply);

	delete[]	Request;
	delete[]	DownloadURI;
	delete[]	Host;
	delete[]	Resource;

	delete[]	PasswordEnc;
	delete[]	SteamUserNameEnc;
	delete[]	ProductNameEnc;
	delete[]	InstallerInfoEnc;
	delete[]	InstallerInfo;

	return		Result;
}

bool	RunInstallScript	(char* ContentArchive, char* MasterServer, char* SteamUserName, ME_TorrentServer* dTorrent, ClassThreadInstall* l_ClassThreadInstall, size_t J)
{
	bool		Result				=	false;

	HTTPParser_Struct	Reply;
	memset(&Reply,0,sizeof(Reply));

	//char*		InstallerInfo		=	BuildString(1,InstallerDetail);
	char*		ContentArchiveEnc	=	EncodeURL(ContentArchive);
	char*		SteamUserNameEnc	=	EncodeURL(SteamUserName);
	
	char*		Resource			=	GetResourceFromURI(MasterServer);
	char*		Host				=	GetHostFromURI(MasterServer);

	char*		DownloadURI			=	BuildString(5,Resource,"?action=getinstallscript&contentarchive=",ContentArchiveEnc,"&username=",SteamUserNameEnc);
	char*		Request				=	BuildString(5,"GET ",DownloadURI," HTTP/1.1\r\nHost: ",Host,"\r\nConnection: Close\r\n\r\n");
	
	dTorrent->TorrentData[J]					=	new	MaxsiTorrentData;
	dTorrent->TorrentData[J]->Server			=	l_ClassThreadInstall->TorrentServer;
	dTorrent->TorrentData[J]->TorrentName		=	BuildString(1,ContentArchiveEnc);
	dTorrent->TorrentData[J]->ContentArchive	=	BuildString(1,ContentArchive);
	dTorrent->TorrentData[J]->DirectoryName		=	BuildString(1,SourceModPath);
	dTorrent->TorrentData[J]->Id				=	J;

	MIS_CurrentlyParsedTorrentId				=	J;


				Reply				=	ConnectAndSendHTTPQuery(Request,strlen(Request),Host);

	if	( Reply.Out )
	{
		// Execute the install script
		char*		InstallScript					=	new char[Reply.OutLenght+1];

		memcpy(InstallScript,Reply.Out,Reply.OutLenght);

		InstallScript[Reply.OutLenght]				=	0;

		/*int			ScriptResult					=*/	MISRun(InstallScript,"MaxsiInstaller");

		delete[]	InstallScript;

		Result										=	GenerateTorrentData();
	}

	DeleteHTTPParser_Struct(Reply);

	delete[]	Request;
	delete[]	DownloadURI;
	delete[]	Host;
	delete[]	Resource;

	delete[]	SteamUserNameEnc;
	delete[]	ContentArchiveEnc;

	return		Result;

}

bool WaitForPIDToClose()
{
	// If we are updating a product, make sure it has stopped running before we update it.
	if ( ProductPID && IsIngame == false )
	{
		char*		PIDSTR		=	UIToA((unsigned int)ProductPID);
		char*		Status		=	BuildString(3,"Waiting for ",ProductName," to close...");
		char*		SubStatus	=	BuildString(7,
			"Maxsi Distribution is currently updating ",ProductName," (Process ID ",PIDSTR,"), "
			"which must be closed before the update can begin, this should happen in a moment. "
			"If you are running ",ProductName, " via a debugger, please make sure the debugger isn't "
			"preventing the process from exiting.");

		StatusFunc(Status);
		SubStatusFunc(SubStatus);

		delete[]	Status;
		delete[]	PIDSTR;

		WaitForProcessToClose(ProductPID);

		SetSubStatus("");
	}
	return true;
}

// Connects to the content server and retrieves the news channel
bool DownloadNewsFunc(char* Host, char* Resource, char* Product, char* Format)
{
	//size_t	ReplySize = 0;

	StatusFunc("Downloading news channel for product...\n");

	char* DownloadURI	=	BuildString(5,Resource,"?action=news&product=",Product,"&format=",Format);
	char* Request		=	BuildString(5,"GET ",DownloadURI," HTTP/1.1\r\nHost: ",Host,"\r\nConnection: Close\r\n\r\n");
	
	delete[] DownloadURI;
	
	HTTPParser_Struct Reply	= ConnectAndSendHTTPQuery(Request,strlen(Request),Host);

	delete[] Request;
	
	if ( Reply.Out && NewsUpdatedFunc)
	{
		// Null-Terminate the output
		char* Terminated = new char[Reply.OutLenght+1];
		memcpy(Terminated,Reply.Out,Reply.OutLenght);
		Terminated[Reply.OutLenght] = 0;
		NewsUpdatedFunc(Terminated);
		delete[] Terminated;
	}

	// Clean up
	DeleteHTTPParser_Struct(Reply);

	return (Reply.Out);
}

bool	OnSpeedUpdate(ME_TorrentServer* Updated, size_t	TorrentId)
{
	char*		SIPrefixes[]	=	{" B", " KiB", " MiB", " GiB", " TiB", " PiB", " EiB" };
	char*		TimePrefixes[]	=	{" Second(s)", " Minute(s)", " Hour(s)", " Day(s)", " Weeks(s)", " Year(s)", " Decade(s)", " \"When I'll be with That Girl\"(s)", " Never"};
	double		Time[]			=	{60.0,3600.0,86400.0,604800.0,31536000.0,315360000.0,3153600000.0,31536000000.0,0.0 };

	double		vComSpeed			=	(double)Updated->GetCompressedSpeed(TorrentId,0,MaxsiTorrent_SpeedDefaultSmooth);
	double		vAvgSpeed			=	(double)Updated->GetCompressedSpeed(TorrentId,0,MaxsiTorrent_SpeedRememberFor);
	double		vRawSpeed			=	(double)Updated->GetUncompressedSpeed(TorrentId,0,MaxsiTorrent_SpeedDefaultSmooth);
	double		vCurDown			=	(double)Updated->DataDownloaded(TorrentId);
	double		vTotalSize			=	(double)Updated->DataRequired(TorrentId); // Only show the amount of data we needed to download at launch - not what we already had.
	double		vLeftSize			=	(double)vTotalSize-vCurDown; 
	double		vTimeLeft			=	0.0;
	
	if ( vAvgSpeed == 0.0 )
	{
		vTimeLeft					=	(double)31536000000.0; // Next milinium!
	}
	else
	{
		vTimeLeft					=	(double)vLeftSize/vAvgSpeed;
	}

	if ( vTimeLeft < 1.0 ) { vTimeLeft = 1.0; }

	size_t		preComSpeed			=	(size_t)floor(log((long double)vComSpeed)/6.931471806);
	size_t		preRawSpeed			=	(size_t)floor(log((long double)vRawSpeed)/6.931471806);
	size_t		preCurDown			=	(size_t)floor(log((long double)vCurDown)/6.931471806);
	size_t		preTotalSize		=	(size_t)floor(log((long double)vTotalSize)/6.931471806);
	size_t		preTimeLeft			=	0;

	while ( true )
	{
		if ( Time[preTimeLeft] == 0.0 ) { break; }
		if ( vTimeLeft < Time[preTimeLeft] ) { break; }
		preTimeLeft++;
	}

	double		Percent				=	(double)((double)vCurDown/(double)vTotalSize)*100.0f;
	ProgressFunc((int)Percent);

	vComSpeed		/=	pow(1024,(double)preComSpeed);
	vRawSpeed		/=	pow(1024,(double)preRawSpeed);
	vCurDown		/=	pow(1024,(double)preCurDown);
	vTotalSize		/=	pow(1024,(double)preTotalSize);
	
	if ( preTimeLeft > 0 )
	{ 
		vTimeLeft		/=	Time[preTimeLeft-1];
	}

	char*		CurrentComSpeed		=	DToA(vComSpeed,4);
	char*		CurrentRawSpeed		=	DToA(vRawSpeed,4);
	char*		CurrentDownloaded	=	DToA(vCurDown,4);
	char*		CurrentTimeLeft		=	DToA(vTimeLeft,3);
	char*		CurrentPercent		=	DToA(Percent,3);
	char*		TotalSize			=	DToA(vTotalSize,4);
	
	char*		Status				=	BuildString(
		9,
		"Downloaded ",
		CurrentDownloaded,
		SIPrefixes[preCurDown],
		" of ",
		TotalSize,
		SIPrefixes[preTotalSize],
		" (",
		CurrentPercent,
		"%)"
	);

	char*		SubStatus			=	BuildString(
		9,
		"Download Speed: ",
		CurrentComSpeed,
		SIPrefixes[preComSpeed],
		"/s (Before Decompression: ",
		CurrentRawSpeed,
		SIPrefixes[preRawSpeed],
		"/s)\nTime Left: ",
		CurrentTimeLeft,
		TimePrefixes[preTimeLeft]
	);

	StatusFunc(Status);
	SubStatusFunc(SubStatus);

	delete[]	SubStatus;
	delete[]	Status;
	delete[]	TotalSize;
	delete[]	CurrentPercent;
	delete[]	CurrentTimeLeft;
	delete[]	CurrentDownloaded;
	delete[]	CurrentRawSpeed;
	delete[]	CurrentComSpeed;

	Win7TaskBarSetState(d_ClassThreadInstall->Parent,TBPF_NORMAL);
	Win7TaskBarSetProgress(d_ClassThreadInstall->Parent,Updated->DataDownloaded(TorrentId),Updated->DataRequired(TorrentId));
	
	return	true;
}

bool	OnBufferExpanded(ME_FileServer* Updated)
{
	return	gTorrentServer->OnBufferExpandedFunc(Updated);
}


// Registers a content server as well as the type of it
// Server [URL] [TYPE]
MISFUNC(Server,MaxsiInstaller)
{
	ClassThreadInstall*	l_ClassThreadInstall	=	0;

	if ( IsIngame )
	{
		l_ClassThreadInstall	=	g_ClassThreadInstall;
	}
	else
	{
		l_ClassThreadInstall	=	d_ClassThreadInstall;
	}

	if (In.Size!=2) { return 0; }
	
	char*	PeerData	=	BuildString(2,"1234",In[0]);
	size_t	PeerDataLen	=	sizeof(int)+strlen(In[0]);	
	
	if ( _stricmp(In[1],"MaxsiDistribution") == 0)
	{
		*(int*)PeerData	=	MaxsiDistributionHTTP;
	}
	else if ( _stricmp(In[1],"TorrentPeer") == 0)
	{
		*(int*)PeerData	=	MaxsiTorrent_MaxProtocol;
	}
	else
	{
		*(int*)PeerData	=	0;
	}

	List	Peers	=	l_ClassThreadInstall->TorrentServer->Peers;

	if ( Peers )
	{
		// See if the content server already is known.
		for ( size_t I = 0; I < GetListSize(Peers); I++)
		{
			char*	Compare		=	(char*)GetListEntry(Peers,I);
			if ( GetListEntrySize(Peers,I) == PeerDataLen+1 &&
				 memcmp(Compare,PeerData,PeerDataLen) == 0 )
			{
				return	0; // Already known.
			}
		}
	}

	l_ClassThreadInstall->TorrentServer->Peers	=	AddListEntry(l_ClassThreadInstall->TorrentServer->Peers,PeerData,PeerDataLen+1);

	return 0;
}

MISFUNC(File,MaxsiInstaller)
{
	ClassThreadInstall*	l_ClassThreadInstall	=	0;

	if ( IsIngame )
	{
		l_ClassThreadInstall	=	g_ClassThreadInstall;
	}
	else
	{
		l_ClassThreadInstall	=	d_ClassThreadInstall;
	}


	if ( !MIS_TempFiles ) { MIS_TempFiles = CreateList(3*100); }

	if ( GetListSize(MIS_TempFiles) <= MIS_TempFilesNum*3 ) { SetListSize(MIS_TempFiles,GetListSize(MIS_TempFiles)+3*100); }

	SetListEntry(MIS_TempFiles,(unsigned int)MIS_TempFilesNum*3+0,In[0],strlen(In[0])+1);
	SetListEntry(MIS_TempFiles,(unsigned int)MIS_TempFilesNum*3+1,In[1],strlen(In[1])+1);
	SetListEntry(MIS_TempFiles,(unsigned int)MIS_TempFilesNum*3+2,In[2],strlen(In[2])+1);

	l_ClassThreadInstall->TorrentServer->TorrentData[MIS_CurrentlyParsedTorrentId]->DataTotal			+=	atoi(In[1]);

	MIS_TempFilesNum++;

	return 0;
}

MISFUNC(BlockHash,MaxsiInstaller)
{	
	if ( !MIS_TempHashes ) { MIS_TempHashes = CreateList(2*1000); }

	if ( GetListSize(MIS_TempHashes) <= MIS_TempHashesNum*2 ) { SetListSize(MIS_TempHashes,GetListSize(MIS_TempHashes)+2*1000); }
	
	SetListEntry(MIS_TempHashes,(unsigned int)MIS_TempHashesNum*2+0,In[0],strlen(In[0])+1);
	SetListEntry(MIS_TempHashes,(unsigned int)MIS_TempHashesNum*2+1,In[1],strlen(In[1])+1);

	MIS_TempHashesNum++;

	return 0;
}

MISFUNC(SuccessfullyRegistered,MaxsiInstaller)
{
	ClassThreadInstall*	l_ClassThreadInstall	=	0;

	if ( IsIngame )
	{
		l_ClassThreadInstall	=	g_ClassThreadInstall;
	}
	else
	{
		l_ClassThreadInstall	=	d_ClassThreadInstall;
	}

	if ( l_ClassThreadInstall )
	{
		l_ClassThreadInstall->ProductRegistered	=	true;
	}	
	else
	{
		ME_UNDEFINED_BEHAVIOR("l_ClassThreadInstaller was NULL in MISFunc SuccessfullyRegistered()!");
	}

	return 0;
}


bool	ValidateBlock(MaxsiTorrentBlock*	Block, MaxsiTorrentFile**	Files)
{
	for (size_t I = Block->FileBegin; I <= (size_t)Block->FileEnd; I++)
	{
		MaxsiTorrentFile*	File		=	Files[I];
		size_t				BlockBegin	=	(Block->Id+0)*MaxsiTorrent_BlockSize;
		//size_t				BlockEnd	=	(Block->Id+1)*MaxsiTorrent_BlockSize-1;

		// See if the file overlaps with the block.
		if ( File->BlockEnd < BlockBegin )
		{
			ME_UNDEFINED_BEHAVIOR("Error #1 linking files with blocks in GenerateTorrentData()!");
			return	false;
		}
		else if ( File->BlockEnd < BlockBegin )
		{
			ME_UNDEFINED_BEHAVIOR("Error #2 linking files with blocks in GenerateTorrentData()!");
			return	false;
		}
	}
	return	true;
}

bool	GenerateTorrentData()
{
	ClassThreadInstall*	l_ClassThreadInstall	=	0;

	if ( IsIngame )
	{
		l_ClassThreadInstall	=	g_ClassThreadInstall;
	}
	else
	{
		l_ClassThreadInstall	=	d_ClassThreadInstall;
	}

	if ( MIS_TempFiles == 0 || MIS_TempHashes == 0 )
	{
		return false;
	}


	bool	Result	=	true;

	ME_TorrentServer*	dTorrent	=	l_ClassThreadInstall->TorrentServer;

	SetListSize(MIS_TempFiles,MIS_TempFilesNum*3);
	SetListSize(MIS_TempHashes,MIS_TempHashesNum*2);

	dTorrent->TorrentData[MIS_CurrentlyParsedTorrentId]->DataRequired	=	dTorrent->TorrentData[MIS_CurrentlyParsedTorrentId]->DataTotal;

	size_t		DataTotal			=	dTorrent->TorrentData[MIS_CurrentlyParsedTorrentId]->DataTotal;
	size_t		HashesAvailable		=	GetListSize(MIS_TempHashes)/2;
	size_t		RequiredBlocks		=	0;
	
	if ( DataTotal % MaxsiTorrent_BlockSize == 0 )
	{
		RequiredBlocks				=	DataTotal/MaxsiTorrent_BlockSize;
	}
	else
	{
		RequiredBlocks				=	DataTotal/MaxsiTorrent_BlockSize+1;
	}

	if ( HashesAvailable != RequiredBlocks )
	{
		char*	ErrorMSG	=	BuildString(3,
			"The downloaded data for ",
			(char*)GetListEntry(DownloadName,MIS_CurrentlyParsedTorrentId),
			" specified an amount of blocks that didn't agree with the required data. HashesAvailable != RequiredBlocks");

		ME_UNDEFINED_BEHAVIOR(ErrorMSG);
		delete[] ErrorMSG;
	}
	else
	{
		dTorrent->TorrentData[MIS_CurrentlyParsedTorrentId]->nBlocks	=	RequiredBlocks;
		dTorrent->TorrentData[MIS_CurrentlyParsedTorrentId]->pBlocks	=	new MaxsiTorrentBlock*[RequiredBlocks];

		for ( size_t I = 0; I < RequiredBlocks; I++)
		{
			dTorrent->TorrentData[MIS_CurrentlyParsedTorrentId]->pBlocks[I]	=	new MaxsiTorrentBlock;

			dTorrent->TorrentData[MIS_CurrentlyParsedTorrentId]->pBlocks[I]->Available		=	0;
			dTorrent->TorrentData[MIS_CurrentlyParsedTorrentId]->pBlocks[I]->Flags			=	0;			
			dTorrent->TorrentData[MIS_CurrentlyParsedTorrentId]->pBlocks[I]->Id				=	I;			
			dTorrent->TorrentData[MIS_CurrentlyParsedTorrentId]->pBlocks[I]->FileBegin		=	-1;			
			dTorrent->TorrentData[MIS_CurrentlyParsedTorrentId]->pBlocks[I]->FileEnd		=	-1;		
			dTorrent->TorrentData[MIS_CurrentlyParsedTorrentId]->pBlocks[I]->TorrentData	=	dTorrent->TorrentData[MIS_CurrentlyParsedTorrentId];
		}
		// We do this in two loops, because before the first loop is completed, we cannot depend on the next loop to complete correctly.
		for ( size_t I = 0; I < RequiredBlocks; I++)
		{
			if ( GetListEntrySize(MIS_TempHashes,I*2+1) != 41 )
			{
				//char*	Entry	=	 (char*)GetListEntry(MIS_TempHashes,I*2+1);
				ME_UNDEFINED_BEHAVIOR("Recieved BlockHash with a lenght that wasn't 40 bytes!");
			}
			else
			{						
				char*	BlockStr	=	(char*)GetListEntry(MIS_TempHashes,I*2);
				size_t	BlockId		=	atoi(BlockStr);
				char*	Dest		=	dTorrent->TorrentData[MIS_CurrentlyParsedTorrentId]->pBlocks[BlockId-1]->SHA1;
				char*	Source		=	(char*)GetListEntry(MIS_TempHashes,I*2+1);
				memcpy(Dest,Source,40);
			}
		}

		// Go through all the files and add them to the proper places.
		// Build a small database so we can look up stuff easily.

		size_t	NumFiles		=	GetListSize(MIS_TempFiles)/3;
		size_t	CurrentOffset	=	0;
		size_t	BytesIntoBlock	=	0;
		//size_t	BlockBeginsAt	=	0;


		dTorrent->TorrentData[MIS_CurrentlyParsedTorrentId]->nFiles		=	NumFiles;
		dTorrent->TorrentData[MIS_CurrentlyParsedTorrentId]->pFiles		=	new MaxsiTorrentFile*[NumFiles];

		MaxsiTorrentBlock**	Blocks	=	dTorrent->TorrentData[MIS_CurrentlyParsedTorrentId]->pBlocks;

		for ( size_t I = 0; I < NumFiles; I++)
		{
			// Create an entry for the file
			char*	FileName	=	(char*)GetListEntry(MIS_TempFiles,I*3+0);
			size_t	FileSize	=	atoi((char*)GetListEntry(MIS_TempFiles,I*3+1));
			char*	SHA1		=	(char*)GetListEntry(MIS_TempFiles,I*3+2);

			MaxsiTorrentFile*	File										=	new MaxsiTorrentFile;
			dTorrent->TorrentData[MIS_CurrentlyParsedTorrentId]->pFiles[I]	=	File;

			File->FileName		=	BuildString(1,FileName);
			File->BlockBegin	=	CurrentOffset;
			File->BlockEnd		=	CurrentOffset+FileSize;

			if ( File->BlockBegin > File->BlockEnd )
			{
				ME_UNDEFINED_BEHAVIOR("GenerateTorrentData() set the start of a file past its end!");
			}

			if ( GetListEntrySize(MIS_TempFiles,I*3+2) != 41 )
			{
				ME_UNDEFINED_BEHAVIOR("Recieved FileHash with a lenght that wasn't 40 bytes!");
			}
			else
			{						
				memcpy(File->SHA1,SHA1,	40);
			}

			CurrentOffset		+=	FileSize;
			BytesIntoBlock		+=	FileSize;

			size_t	Offset		=	0;

			while ( BytesIntoBlock > MaxsiTorrent_BlockSize )
			{
				size_t	BlockId	=	(CurrentOffset-FileSize)/MaxsiTorrent_BlockSize+Offset;

				if ( Blocks[BlockId]->FileBegin	==	-1)
				{
					Blocks[BlockId]->FileBegin	=	I;
				}

				Blocks[BlockId]->FileEnd	=	I;

				ValidateBlock(Blocks[BlockId],dTorrent->TorrentData[MIS_CurrentlyParsedTorrentId]->pFiles);

				BytesIntoBlock	-=	MaxsiTorrent_BlockSize;
				Offset			+=	1;
			}

			if ( BytesIntoBlock )
			{
				size_t	BlockId	=	CurrentOffset/MaxsiTorrent_BlockSize;

				if ( Blocks[BlockId]->FileBegin	==	-1)
				{
					Blocks[BlockId]->FileBegin	=	I;
				}

				Blocks[BlockId]->FileEnd	=	I;

				ValidateBlock(Blocks[BlockId],dTorrent->TorrentData[MIS_CurrentlyParsedTorrentId]->pFiles);
			}
		}
	}

	DeleteList(MIS_TempHashes);		MIS_TempHashes	=	0;
	DeleteList(MIS_TempFiles);		MIS_TempFiles	=	0;

	MIS_TempFilesNum	=	0;
	MIS_TempHashesNum	=	0;

	return Result;
}

// Detect the state of the file we are interested in.
bool	CheckFileStateReliable(MaxsiTorrentFile* File, MaxsiTorrentData*	Data)
{
	bool	Result		=	true;

	char*	FileName	=	BuildString(2,Data->DirectoryName,File->FileName);

	HANDLE	FileHandle	=	CreateFileA(FileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	if ( FileHandle == INVALID_HANDLE_VALUE )
	{
		// Whoops! A file is missing.
		Result	=	false;
	}
	else
	{
		size_t	FileLen			=	GetFileSize(FileHandle, NULL);
		size_t	DesiredFileLen	=	File->BlockEnd - File->BlockBegin;

		if ( FileLen != DesiredFileLen )
		{
			// The file size isn't identical - thus the file should not be the 
			// file we wish for. Let's just assume that the entire file is invalid.
			Result	=	false;
		}
		else
		{
			// Right! Let's read the contents of the file into the buffer

			char*		Buffer	=	new char[FileLen];

			DWORD		BytesRead;

			ReadFile(FileHandle,Buffer,FileLen,&BytesRead,NULL);

			char*		SHA1	=	CalculateSHA1(Buffer,FileLen);

			if ( memcmp(File->SHA1,SHA1,40) != 0 )
			{
				Result	=	false;
			}

			delete[]	SHA1;
			delete[]	Buffer;
		}

		CloseHandle(FileHandle);
	}		

	return	Result;
}

// Detect the state of the block we are interested in.
bool	CheckBlockStateReliable(MaxsiTorrentBlock* Block, char* Buffer)
{
	bool	Result	=	true;

	size_t	Read				=	0;	
	MaxsiTorrentFile*	File	=	0;

	for ( size_t I = Block->FileBegin; I <= (size_t)Block->FileEnd && Result; I++)
	{
		// Get the filename.
				File		=	Block->TorrentData->pFiles[I];
		char*	FileName	=	BuildString(2,Block->TorrentData->DirectoryName,File->FileName);

		HANDLE	FileHandle	=	CreateFileA(FileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

		str_replace(FileName,"/","\\");

		if ( FileHandle == INVALID_HANDLE_VALUE )
		{
			// Whoops! A file is missing from the block - thus the block isn't downloaded.
			// While some of the other files might be present - Let's just assume
			// it's not worth the while to detect if that's the case and download the whole
			// block all over again. After all it's only MaxsiTorrent_BlockSize bytes!
			Result	=	false;
		}
		else
		{
			size_t	FileLen			=	GetFileSize(FileHandle, NULL);
			size_t	DesiredFileLen	=	File->BlockEnd - File->BlockBegin;

			if ( FileLen != DesiredFileLen )
			{
				// The file size isn't identical - thus the file should not be the 
				// file we wish for. Let's just assume that the entire file is invalid.
				Result	=	false;
			}
			else
			{
				// Right! Let's read the contents of the file (which overlaps with the block) into
				// the buffer. We'll SHA-1 compare it later. First, we'll detect where we are reading
				// in the file.

				size_t	SeekPos		=	0;
				
				if ( MaxsiTorrent_BlockSize*Block->Id  > File->BlockBegin )
				{
					SeekPos			=	MaxsiTorrent_BlockSize*Block->Id	-	File->BlockBegin;
				}

				size_t	NumBytes	=	FileLen-SeekPos;

				if ( NumBytes > MaxsiTorrent_BlockSize-Read )
				{
					NumBytes		=	MaxsiTorrent_BlockSize-Read;
				}

				if ( SeekPos+NumBytes > FileLen )
				{
					__debugbreak();
				}

				// Then let's read the real contents of the file!
				
				if ( Read + NumBytes > MaxsiTorrent_BlockSize )
				{
					__debugbreak();
				}
				//size_t	End			=	Read+NumBytes;
				//size_t	eof			=	SeekPos+NumBytes;
				DWORD	BytesRead;
				SetFilePointer(FileHandle, SeekPos, NULL, FILE_BEGIN);
				ReadFile(FileHandle,Buffer+Read,NumBytes,&BytesRead,NULL);
				Read	+=	NumBytes;
			}

			CloseHandle(FileHandle);
		}		

		// Clean up
		delete[]	FileName;
	}

	// Only bother SHA-1 Check the block everything appears to be in order.
	if ( Result )
	{	// Calculate the SHA-1 Hash

		if ( Read != MaxsiTorrent_BlockSize )
		{
			if ( Block->Id	!=	Block->TorrentData->nBlocks-1 )
			{
				ME_UNDEFINED_BEHAVIOR("Size of non-last block wasn't MaxsiTorrent_BlockSize!");
			}
		}

		char*		SHA1	=	CalculateSHA1(Buffer,Read);

		if ( memcmp(Block->SHA1,SHA1,40) != 0 )
		{
			Result	=	false;


			ME_DONT_COMPILE_RELEASE_BUILD();

#ifdef _DEBUGz

			bool	FoundProblem	=	false;

			for (size_t N = Block->FileBegin; N <= Block->FileEnd; N++)
			{
				if ( CheckFileStateReliable(Block->TorrentData->pFiles[N],Block->TorrentData) == false )
				{
					// The problem was Block->TorrentData->pFiles[N]
					int	leet		=	1337;
					FoundProblem	=	true;
				}
			}

			if ( FoundProblem == false )
			{
				ME_UNDEFINED_BEHAVIOR("CheckBlockStateReliable()'s internal sanity check failed!");
			}

#endif
		}
		else
		{
			Block->TorrentData->DataRequired	-=	Read;
		}
		delete[]	SHA1;
	}

	return Result;
}

// Detect the states of all the blocks in this torrent. (Whether all files are downloaded - and how far we are)
bool	CheckBlockStatesReliable(MaxsiTorrentData* Data)
{
	ProgressFunc(0);

	if (!Data) { return false; }
	if (!Data->pFiles || !Data->nFiles)		{ return false; }
	if (!Data->pBlocks || !Data->nBlocks)	{ return false; }

	char*		Buffer	=	new char[MaxsiTorrent_BlockSize];

	for (size_t I = 0; I < Data->nBlocks; I++)
	{
		if ( !Data->pBlocks[I] ) { continue; }

		ProgressFunc(I*100/Data->nBlocks);

		if ( CheckBlockStateReliable(Data->pBlocks[I],Buffer) )
		{
			Data->pBlocks[I]->Flags	|=	Block_Written;
		}
	}

	delete[]	Buffer;

	ProgressFunc(100);

	return true;
}

bool	CheckBlockStatesFast(MaxsiTorrentData* Data)
{
	// Todo!
	return CheckBlockStatesReliable(Data);
}

bool	OutputContentArchiveState(MaxsiTorrentData* Data)
{
	char*	MasterServerEnc	=	EncodeFileName(MasterServer);
	char*	FileName		=	BuildString(6,MDRoot,"Products/",MasterServerEnc,"/",Data->TorrentName,".mis");
	char*	MDProducts		=	BuildString(2,MDRoot,"Products/");
	char*	MDMasterDir		=	BuildString(4,MDRoot,"Products/",MasterServerEnc,"/");

	str_replace(FileName,"/","\\");

	mkdir(MDRoot);
	mkdir(MDProducts);
	mkdir(MDMasterDir);

	delete[]	MasterServerEnc;
	delete[]	MDMasterDir;

	if ( remove(FileName) != 0 )
	{
		//int leet = 1337;
		//ME_DONT_COMPILE_RELEASE_BUILD();
	}

	HANDLE	FileHandle	=	CreateFileA(FileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	if ( FileHandle != INVALID_HANDLE_VALUE )
	{
		char*	Header	=
			"// Generated by x-MaxsiEngineMaxsiInstaller\r\n"
			"// List of files in products installed by MaxsiDistribution in this folder\r\n\r\n"

			"// AUTOMATICALLY GENERATED - DO NOT HAND EDIT\r\n\r\n"

			"Application/x-MaxsiEngineInstallerScript/2.0\r\n"
			"Application: x-MaxsiEngineMaxsiInstaller\r\n\r\n";

		DWORD	BytesWritten;

		WriteFile(FileHandle, Header, strlen(Header), &BytesWritten, NULL);
		WriteFile(FileHandle, "Root \"", strlen("Root \""), &BytesWritten, NULL);
		WriteFile(FileHandle, Data->DirectoryName, strlen(Data->DirectoryName), &BytesWritten, NULL);
		WriteFile(FileHandle, "\"\r\n", strlen("\"\r\n"), &BytesWritten, NULL);

		char	SHA1STR[41];

		for ( size_t I = 0; I < Data->nFiles; I++ )
		{
			char*	FileSizeSTR		=	UIToA((unsigned int)Data->pFiles[I]->BlockEnd-Data->pFiles[I]->BlockBegin);
					SHA1STR[40]		=	0;
			memcpy(	SHA1STR,Data->pFiles[I]->SHA1,40);
			char*	Line	=	BuildString(7,"File \"",Data->pFiles[I]->FileName,"\" ",FileSizeSTR," ",SHA1STR,"\r\n");
			WriteFile(FileHandle, Line, strlen(Line), &BytesWritten, NULL);
			delete[]	Line;
			delete[]	FileSizeSTR;
		}

		CloseHandle(FileHandle);
	}

	delete[]	FileName;
	delete[]	MDProducts;

	return true;
}